package com.htfyun.serialport.core;

import android.content.res.Resources;
import android.serialport.SerialPort;
import android.util.Log;

import com.htfyun.serialport.utils.SerialDebugLog;
import com.htfyun.serialport.utils.UsefulUtil;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;

public enum SerialPortManager {
    INSTANCE,;

    private Map<UUID, OnSerialPortDataListener> serialPortDataListenerMap = new ConcurrentHashMap<>();


    /**
     * 注册监听, 并打开串口
     *
     * @param listener
     * @return 返回的uuid唯一码, 作为读写串口的token.</p>
     *          注意: 如果返回的此值丢失, 后果是: </p>
     *              1. 那么就不能写串口了, 应该可读到串口数据; </p>
     *              2. 串口也不会被关闭了.
     */
    public UUID registerSerialPortDataListener(OnSerialPortDataListener listener) {

        Iterator<Map.Entry<UUID, OnSerialPortDataListener>> entries = serialPortDataListenerMap.entrySet().iterator();

        while (entries.hasNext()) {
            Map.Entry<UUID, OnSerialPortDataListener> entry = entries.next();
            if (entry.getValue().equals(listener)) {
                return entry.getKey();
            }
        }

        UUID uuidToke = UUID.randomUUID();
        serialPortDataListenerMap.put(uuidToke, listener);

        startListener();

        return uuidToke;
    }

    /**
     * 注销监听, 如果没有监听, 那么关闭串口
     * @param uuidToke 此值是{@link #unregisterSerialPortDataListener(UUID)} <p></p>
     *
     */
    public void unregisterSerialPortDataListener(UUID uuidToke) {
        serialPortDataListenerMap.remove(uuidToke);

        if (serialPortDataListenerMap.isEmpty()) {
            stopListener();
        }
    }

    public boolean isRegisteredSerialPortDataListener(UUID uuidToke) {
        OnSerialPortDataListener listener = serialPortDataListenerMap.get(uuidToke);
        return listener != null;
    }


    public void writeSerialPort(UUID uuidToke, byte[] writeData) {

        ByteBuffer byteBuffer = ByteBuffer.wrap(writeData);

        writeSerialPort(uuidToke, byteBuffer);
    }

    public void writeSerialPort(UUID uuidToke, byte[] writeData, int writeLen) {

        ByteBuffer byteBuffer = ByteBuffer.wrap(writeData, 0, writeLen);

        writeSerialPort(uuidToke, byteBuffer);
    }


    public void writeSerialPort(UUID uuidToke, ByteBuffer byteBuffer) {

        addWritePayload(new SerialPortPayload(uuidToke, byteBuffer));
    }


    //////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////

    private void onSerialPortWriteAction(UUID uuidToke, ByteBuffer byteBuffer) {

        OnSerialPortDataListener listener = serialPortDataListenerMap.get(uuidToke);
        if (listener != null) {
            listener.onWriteData(uuidToke, byteBuffer);
        }
    }

    private void onSerialPortReadAction(byte[] readData, int readLen) {

        Iterator<Map.Entry<UUID, OnSerialPortDataListener>> entries = serialPortDataListenerMap.entrySet().iterator();

        while (entries.hasNext()) {
            Map.Entry<UUID, OnSerialPortDataListener> entry = entries.next();
            entry.getValue().onReadData(entry.getKey(), readData, readLen);
        }

    }

    private BlockingQueue<SerialPortPayload> writePayloadQueue = new LinkedBlockingQueue<>();
    private ExecutorService executorService;
    private boolean writeEnabled = false;
    private boolean readEnabled = false;

    private volatile boolean isRunning = false;

    private final int INTERVAL_BETWEEN_WRITE_UART_ms = 2;


    private void startListener() {

        if (isRunning) {
            return;
        }

        isRunning = true;

        if (executorService != null) {
            executorService.shutdownNow();
            writePayloadQueue.clear();
            executorService = null;
        }

        executorService = Executors.newFixedThreadPool(2);

        if (mSerialPort == null) {
            openSerialPort();
        }

        executorService.execute(writeRunnable);
        executorService.execute(readRunnable);

    }

    private void stopListener() {

        if (!isRunning) {
            return;
        }

        isRunning = false;

        writeEnabled = false;
        readEnabled = false;

        if (executorService != null) {
            executorService.shutdownNow();
            writePayloadQueue.clear();
            executorService = null;
        }

        if (mSerialPort != null) {
            closeSerialPort();
        }

    }

    private void addWritePayload(SerialPortPayload portPayload) {
        writePayloadQueue.add(portPayload);
    }

    private Runnable writeRunnable = new Runnable() {
        @Override
        public void run() {
            writeEnabled = true;
            while (writeEnabled) {
                try {
                    SerialPortPayload portPayload = writePayloadQueue.take();
                    if (portPayload != null) {
                        writeSerialPort(portPayload.byteBuffer);
                        onSerialPortWriteAction(portPayload.uuidToken, portPayload.byteBuffer);
                    }

                    Thread.sleep(INTERVAL_BETWEEN_WRITE_UART_ms);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    };

    private Runnable readRunnable = new Runnable() {
        @Override
        public void run() {
            readEnabled = true;
            byte[] readData = new byte[64];
            while (readEnabled) {
                int size = readSerialPort(readData);
                SerialDebugLog.e("raw read data = " + UsefulUtil.toHexString(readData, size));
                onSerialPortReadAction(readData, size);
            }
        }
    };


    private final class SerialPortPayload {
        private final UUID uuidToken;
        private final ByteBuffer byteBuffer;

        private SerialPortPayload(UUID uuidToken, ByteBuffer byteBuffer) {
            this.uuidToken = uuidToken;
            this.byteBuffer = byteBuffer;
        }

    }

    private final int BAUDRATE = 115200;
    private final String DEVICE_PATH = "/dev/ttyS2";
    private SerialPort mSerialPort = null;

    private final static boolean DEBUG = true;

    private synchronized void openSerialPort() {
        try {
            mSerialPort = new SerialPort(DEVICE_PATH, BAUDRATE);
            SerialPortExtraControl.setServoPowerOn(true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private synchronized void closeSerialPort() {
        try {
            mSerialPort.getOutputStream().close();
            mSerialPort.getInputStream().close();
            mSerialPort.closeSerialPort();
            mSerialPort = null;
            SerialPortExtraControl.setServoPowerOn(false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private int readSerialPort(byte[] readData) {
        if (mSerialPort == null || mSerialPort.getInputStream() == null) {
            return -1;
        }

        try {
            return mSerialPort.getInputStream().read(readData);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return -1;
    }

    private synchronized void writeSerialPort(ByteBuffer byteBuffer) {

        if (DEBUG) {
            if (byteBuffer != null) {
                SerialDebugLog.w("writeSerialPort byteBuffer = " + UsefulUtil.toHexString(byteBuffer.array()));

            }
        }
        if (mSerialPort == null || mSerialPort.getOutputStream() == null || byteBuffer == null) {
            return;
        }
        try {
            mSerialPort.getOutputStream().write(byteBuffer.array());
            mSerialPort.getOutputStream().flush();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}
